﻿using System;
using System.Collections.Generic;
using System.Drawing.Printing;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Management;
using System.IO;
using System.Net.Sockets;
using NLog.Fluent;
using Microsoft.Win32;
using System.Collections;
using System.Printing;

namespace Printer
{
    internal class PrinterHelper
    {
        public float xpos { get; set; }
        public float ypos { get; set; }
        /// <summary>
        /// 指定打印机名打印
        /// </summary>
        /// <param name="PrinterName"></param>
        public void Print(string PrinterName)
        {
            PrintDocument doc = new PrintDocument();
            doc.PrinterSettings.PrinterName = PrinterName;
            doc.PrintPage += new PrintPageEventHandler(PrintHandler);//打印机页面内容设置
            doc.Print();
        }

        private void PrintHandler(object sender, PrintPageEventArgs ppeArgs)
        {
            Font FontNormal = new Font("Verdana", 12);
            Graphics g = ppeArgs.Graphics;
            g.DrawString("Your string to print", FontNormal, Brushes.Black, xpos, ypos, new StringFormat());
        }
        /// <summary>
        /// 获取已安装的打印机驱动名
        /// </summary>
        /// <returns></returns>
        public IEnumerable<string> GetPrintDrivers()
        {
            var dirvers = new List<string>();
            //Query printer's portName from WIN32_Printer
            string query = string.Format("SELECT * from Win32_PrinterDriver");
            var searcher = new ManagementObjectSearcher(query);
            ManagementObjectCollection managementObjects = searcher.Get();
            foreach (ManagementObject managementObject in managementObjects)
            {
                var name = managementObject.Properties["Name"].Value;
                dirvers.Add(name.ToString());
            }
            return dirvers;
        }

        
    }
    /// <summary>
    /// 连接打印机并发送指令代码
    /// </summary>
    public class PrinterCommunicate
    {
        public bool CheckNetWorkConnection(string strPrinterIP, int intPrinterPort)
        {
            System.Net.Sockets.TcpClient Zebraclient = new TcpClient();
            try
            {
                Zebraclient.Connect(strPrinterIP, intPrinterPort);
                return Zebraclient.Connected;
            }
            catch
            {
                return false;
            }
        }
        public bool SendZPL_ViaNetwork(string strPrinterIP, int intPrinterPort, string strPrinterCommand, out string strOutMsg)
        {
            strOutMsg = "";
            System.Net.Sockets.TcpClient Zebraclient = new TcpClient();
            try
            {
                Zebraclient.SendTimeout = 1500;
                Zebraclient.ReceiveTimeout = 1500;
                //defining ip address and port number
                Zebraclient.Connect(strPrinterIP, intPrinterPort);
                if (Zebraclient.Connected == true)
                {
                    //send and receive illustrated below
                    NetworkStream mynetworkstream;
                    StreamReader mystreamreader;
                    StreamWriter mystreamwriter;
                    mynetworkstream = Zebraclient.GetStream();
                    mystreamreader = new StreamReader(mynetworkstream);
                    mystreamwriter = new StreamWriter(mynetworkstream);
                    mystreamwriter.WriteLine(strPrinterCommand);
                    mystreamwriter.Flush();
                    char[] mk = null;
                    mk = new char[256];
                    mystreamreader.Read(mk, 0, mk.Length);
                    string data1 = new string(mk);
                    strOutMsg = data1;
                    Zebraclient.Close();
                    return true;
                }
                else
                {
                    strOutMsg = "Connection failed";
                    return false;
                }
            }
            catch (Exception ex)
            {
                //Log.WriteLogToFile("IPP_PCL", "PrinterCommunicate.cs -- SendZPL_ViaNetwork", "-99", ex.Message);
                strOutMsg = "EXCEPTION_ERROR";
            }
            return false;
        }
    }


    /// <summary>
    /// 管理打印机的状态以及查询修改打印机属性
    /// </summary>
    class PrinterPropertyManager
    {
        /// <summary>
        /// 获取打印机的IP地址和端口号
        /// </summary>
        /// <param name="printerName">打印机名称</param>
        public KeyValuePair<string, int> GetPrinterIPAndPort(string printerName)
        {
            string port = GetPrinterPropertyValue(printerName, "PortName");
            //Query portName's property from regedit
            string[] portQuerys = GetPortQuerys(port);
            foreach (var portQuery in portQuerys)
            {
                RegistryKey portKey = Registry.LocalMachine.OpenSubKey(portQuery, RegistryKeyPermissionCheck.Default,
                                          System.Security.AccessControl.RegistryRights.QueryValues);
                if (portKey != null)
                {
                    /*             
                     * 取IP的时候要特别注意，如果端口类型为"Advanced Port Monitor"，那么IP地址会保存到IPAddress属性中                         
                     * 如果端口类型为"Standard Tcp/Ip Port",那么IP地址会保存到HostName属性中。
                     */
                    object IPValue = portKey.GetValue("IPAddress", String.Empty,
                                         RegistryValueOptions.DoNotExpandEnvironmentNames);
                    object portValue = portKey.GetValue("PortNumber", String.Empty,
                                           RegistryValueOptions.DoNotExpandEnvironmentNames);
                    if (IPValue != null && portValue != null)
                    {
                        return new KeyValuePair<string, int>(IPValue.ToString(), (Int32)portValue);
                    }
                }
            }
            return new KeyValuePair<string, int>();
        }
        /// <summary>
        /// determine whether the printer is network printer.
        /// </summary>
        public bool IsNetWorkPrinter(string printer)
        {
            string port = GetPrinterPropertyValue(printer, "PortName");
            //Query portName's property from regedit
            string[] portQuerys = GetNetWorkPortQuerys(port);
            foreach (var portQuery in portQuerys)
            {
                RegistryKey portKey = Registry.LocalMachine.OpenSubKey(portQuery, RegistryKeyPermissionCheck.Default,
                                          System.Security.AccessControl.RegistryRights.QueryValues);
                if (portKey != null)
                {
                    return true;
                }
            }
            return false;
        }
        private string[] GetNetWorkPortQuerys(string portName)
        {
            return new string[]
            {
                @"System\CurrentControlSet\Control\Print\Monitors\Advanced Port Monitor\Ports\" + portName,
                @"System\CurrentControlSet\Control\Print\Monitors\Standard TCP/IP Port\Ports\" + portName
            };
        }
        private string[] GetPortQuerys(string portName)
        {
            return new string[]
            {
                @"System\CurrentControlSet\Control\Print\Monitors\Advanced Port Monitor\Ports\" + portName,
                @"System\CurrentControlSet\Control\Print\Monitors\Local Port\Ports\" + portName,
                @"System\CurrentControlSet\Control\Print\Monitors\Standard TCP/IP Port\Ports\" + portName,
                @"System\CurrentControlSet\Control\Print\Monitors\USB Monitor\Ports\" + portName,
                @"System\CurrentControlSet\Control\Print\Monitors\WSD Port\Ports\" + portName,
            };
        }
        /// <summary>
        /// get printer property value
        /// 使用WMI查询打印机的信息，需要打开windows management instrumentation服务
        /// </summary>
        public string GetPrinterPropertyValue(string printerName, string propertyName)
        {
            string propertyValue = string.Empty;
            //Query printer's portName from WIN32_Printer
            string query = string.Format("SELECT * from Win32_Printer WHERE Name = '{0}'", printerName);
            ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);
            ManagementObjectCollection managementObjects = searcher.Get();
            foreach (ManagementObject managementObject in managementObjects)
            {
                PropertyData propertyData = managementObject.Properties[propertyName];
                if (propertyData != null)
                {
                    propertyValue = propertyData.Value.ToString();
                }
            }
            return propertyValue;
        }
        /// <summary>
        /// change printer property value
        /// </summary>
        public void SetPrinterPropertyValue(string printerName, string propertyName, string propertyValue)
        {
            //Query printer's portName from WIN32_Printer
            string query = string.Format("SELECT * from Win32_Printer WHERE Name = '{0}'", printerName);
            ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);
            ManagementObjectCollection managementObjects = searcher.Get();
            foreach (ManagementObject managementObject in managementObjects)
            {
                PropertyData propertyData = managementObject.Properties[propertyName];
                if (propertyData != null)
                {
                    propertyData.Value = propertyValue;
                    managementObject.Put();
                }
            }
        }
        /// <summary>
        /// whether the port is existed
        /// 检查某个打印端口是否存在
        /// </summary>
        /// <param name="printerName"></param>
        /// <param name="port"></param>
        /// <returns></returns>
        public bool IsPortExisted(string printerName, string port)
        {
            string propertyName = "PortName";
            string currentPort = null;
            try
            {
                currentPort = GetPrinterPropertyValue(printerName, propertyName);
                SetPrinterPropertyValue(printerName, propertyName, port);
                SetPrinterPropertyValue(printerName, propertyName, currentPort);
            }
            catch (Exception ex)
            {
                return false;
            }
            return true;
        }
        /// <summary>
        /// 获取打印机名字的列表
        /// </summary>
        public ArrayList GetPrinterNames()
        {
            ArrayList result = new ArrayList();
            foreach (string ss in PrinterSettings.InstalledPrinters)
            {
                result.Add(ss);
            }
            return result;
        }
        /// <summary>
        /// 获取打印机状态
        /// </summary>
        /// <param name="printerName">打印机名称</param>
        public PrinterStatus GetPrinterStatus(string printerName, out bool isError, out string errorDescription)
        {
            //init return variable
            isError = false;
            errorDescription = string.Empty;
            PrinterStatus printerStatus = PrinterStatus.Idle;
            if (IsNetWorkPrinter(printerName))
            {
                KeyValuePair<string, int> ipPortKeyValuePair = GetPrinterIPAndPort(printerName);
                PrinterCommunicate printerCommunicate = new PrinterCommunicate();
                if (printerCommunicate.CheckNetWorkConnection(ipPortKeyValuePair.Key, ipPortKeyValuePair.Value))
                {

                    WindowsPrintQueue winowsPrintQueue = new WindowsPrintQueue();
                    if (winowsPrintQueue.IsZebraPrinter(printerName))
                    {
                        //get actual status of zebra printer via zebra command
                        if (IsPause(ipPortKeyValuePair.Key, ipPortKeyValuePair.Value))
                        {
                            printerStatus = PrinterStatus.Paused;
                        }
                        string errorMsg = string.Empty;
                        if (IsError(ipPortKeyValuePair.Key, ipPortKeyValuePair.Value, out errorMsg))
                        {
                            isError = true;
                            errorDescription = GetZebraPrinterErrorStatusDescription(errorMsg);
                        }
                    }
                }
                else
                {
                    //not connected
                    printerStatus = PrinterStatus.Offline;
                }
            }
            return printerStatus;
        }
        /// <summary>
        /// determine whether the network printer is in pause.Only for zebra model printer
        /// </summary>
        private bool IsPause(string ip, int port)
        {
            string strOutMsg = null;
            string zebraCommand = "^XA~HS^XZ";
            PrinterCommunicate printerCommunicate = new PrinterCommunicate();
            if (printerCommunicate.SendZPL_ViaNetwork(ip, port, zebraCommand, out strOutMsg))
            {
                //split retMsg with "\r\n"
                string[] retMsgs = strOutMsg.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
                if (retMsgs != null)
                {
                    string retFirstMsgItem = retMsgs[0];
                    string[] retFirstMsgItems = retFirstMsgItem.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
                    return "1".Equals(retFirstMsgItems[2]);
                }
            }
            return false;
        }
        /// <summary>
        /// determine whether the network printer is in error.only for zebra model printer
        /// </summary>
        /// <param name="ip"></param>
        /// <param name="port"></param>
        /// <param name="strOutMsg"></param>
        /// <returns></returns>
        private bool IsError(string ip, int port, out string strOutMsg)
        {
            strOutMsg = string.Empty;
            string zebraCommand = "^XA~HQES^XZ";
            PrinterCommunicate printerCommunicate = new PrinterCommunicate();
            if (printerCommunicate.SendZPL_ViaNetwork(ip, port, zebraCommand, out strOutMsg))
            {
                //split retMsg with "\r\n"
                string[] retMsgs = strOutMsg.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
                if (retMsgs != null)
                {
                    for (int i = 0; i < retMsgs.Length; i++)
                    {
                        string retMsgItem = retMsgs[i];
                        if (string.IsNullOrEmpty(retMsgItem) || !retMsgItem.Contains(":")) { continue; }
                        string[] retMsgItemSplited = retMsgItem.Split(new string[] { ":" }, StringSplitOptions.RemoveEmptyEntries);
                        if (retMsgItemSplited == null || retMsgItemSplited.Length == 0) { continue; }
                        string errorMsg = retMsgItemSplited[1].Trim();
                        if (!string.IsNullOrEmpty(errorMsg))
                        {
                            string errorFlag = errorMsg.Substring(0, 1);
                            if ("1".Equals(errorFlag))
                            {
                                strOutMsg = errorMsg;
                                return true;
                            }
                        }
                    }
                }
            }
            return false;
        }
        /// <summary>
        /// get actual status of zebra printer via zebra command.
        /// </summary>
        /// <param name="ip"></param>
        /// <param name="port"></param>
        /// <returns></returns>
        private string GetZebraPrinterErrorStatusDescription(string errorMsg)
        {
            StringBuilder status = new StringBuilder();
            //error happend
            string nibble1 = errorMsg.Substring(errorMsg.Length - 1, 1);
            string nibble2 = errorMsg.Substring(errorMsg.Length - 2, 1);
            string nibble3 = errorMsg.Substring(errorMsg.Length - 3, 1);
            if (!"0".Equals(nibble1))
            {
                Dictionary<int, string> nibble1ErrorDictionary = new Dictionary<int, string>();
                nibble1ErrorDictionary.Add(1, "Midea Out");
                nibble1ErrorDictionary.Add(2, "Ribbon Out");
                nibble1ErrorDictionary.Add(4, "Head Open");
                nibble1ErrorDictionary.Add(8, "Cutter Fault");
                status.Append(GetErrorDescriptionFromNibble(nibble1, nibble1ErrorDictionary));
            }
            if (!"0".Equals(nibble2))
            {
                Dictionary<int, string> nibble2ErrorDictionary = new Dictionary<int, string>();
                nibble2ErrorDictionary.Add(1, "Printhead Over Temperature");
                nibble2ErrorDictionary.Add(2, "Motor Over Temperature");
                nibble2ErrorDictionary.Add(4, "Bad Printhead Element");
                nibble2ErrorDictionary.Add(8, "Printhead Detection Error");
                status.Append(GetErrorDescriptionFromNibble(nibble1, nibble2ErrorDictionary));
            }
            if (!"0".Equals(nibble3))
            {
                Dictionary<int, string> nibble3ErrorDictionary = new Dictionary<int, string>();
                nibble3ErrorDictionary.Add(1, "Invalid Firmware Config");
                nibble3ErrorDictionary.Add(2, "Printhead Thermistor Open");
                status.Append(GetErrorDescriptionFromNibble(nibble1, nibble3ErrorDictionary));
            }
            string strStatus = status.ToString();
            return strStatus.Substring(0, strStatus.Length - 1);
        }
        private StringBuilder GetErrorDescriptionFromNibble(string nibble, Dictionary<int, string> statusDictionary)
        {
            int intNibble = Convert.ToInt32(nibble);
            StringBuilder status = new StringBuilder();
            if (statusDictionary != null)
            {
                foreach (var statusKV in statusDictionary)
                {
                    if ((intNibble & statusKV.Key) == statusKV.Key)
                    {
                        status.Append(statusKV.Value);
                        status.Append(",");
                    }
                }
            }
            return status;
        }
    }


    public enum PrinterStatus
    {
        Other = 1,
        Unknown = 2,
        Idle = 3,
        Printing = 4,
        Warmup = 5,
        Paused = 6,
        Offline = 7
    }
    /// <summary>
    /// 用于获取打印机的型号，以及得到打印机的WindowsPrintQueue(打印队列)
    /// </summary>
    public class WindowsPrintQueue
    {
        /// <summary>
        /// whether the two printer is same model.
        /// </summary>
        /// <param name="printerName1"></param>
        /// <param name="printerName2"></param>
        /// <returns></returns>
        public bool IsSameModel(string printerName1, string printerName2)
        {
            return GetPrinterModel(printerName1).Equals(GetPrinterModel(printerName2));
        }
        /// <summary>
        /// whether the printer is zebra model.
        /// </summary>
        /// <param name="printerName1"></param>
        /// <param name="printerName2"></param>
        /// <returns></returns>
        public bool IsZebraPrinter(string printerName)
        {
            string zebraModel = "ZEBRA";
            return GetPrinterModel(printerName).Contains(zebraModel);
        }
        /// <summary>
        /// Return printer model
        /// </summary>
        /// <param name="printerName"></param>
        /// <returns></returns>
        public string GetPrinterModel(string printerName)
        {
            string model = string.Empty;
            PrintQueue printQueue = GetPrintQueue(printerName);
            if (printQueue != null)
            {
                //Get printer model
                if (printQueue.Description.IndexOf(",") == printQueue.Description.LastIndexOf(","))
                {
                    model = printQueue.Description.Substring(printQueue.Description.IndexOf(",") + 1,
                                printQueue.Description.LastIndexOf(",") - printQueue.Description.IndexOf(",") - 1);
                }
                else
                {
                    model = printQueue.Description.Substring(printQueue.Description.IndexOf(",") + 1);
                }
            }
            return model;
        }
        /// <summary>
        /// Get Windows Print Queue via printer name
        /// </summary>
        /// <param name="printerName"></param>
        /// <returns></returns>
        public System.Printing.PrintQueue GetPrintQueue(string printerName)
        {
            System.Printing.PrintQueue printQueue = null;
            PrintServer server = new PrintServer(printerName);
            foreach (System.Printing.PrintQueue pq in server.GetPrintQueues())
            {
                if (pq.FullName.Equals(printerName))
                {
                    printQueue = pq;
                }
            }
            return printQueue;
        }
        /// <summary>
        /// Get Windows Print Queue via printer name
        /// 如果两个printer指向的是同一个物理打印机,则如果copy1的printQueue已经打开,
        ///则发送到copy2的打印job也会添加到已经打开的copy1的printQueue中.
        /// </summary>
        /// <param name="printerName"></param>
        /// <returns></returns>
        public System.Printing.PrintQueue GetOpenedPrintQueueOfSameModel(string printerName)
        {
            System.Printing.PrintQueue doorOpenedprintQueue = null;
            System.Printing.PrintQueue currentPrinterPrintQueue = null;
            PrintServer server = new PrintServer(printerName);
            foreach (System.Printing.PrintQueue pq in server.GetPrintQueues())
            {
                if (pq.FullName.Equals(printerName))
                {
                    currentPrinterPrintQueue = pq;
                }
                else
                {
                    if (IsSameModel(printerName, pq.FullName))
                    {
                        if (pq.IsDoorOpened)
                        {
                            doorOpenedprintQueue = pq;
                            break;
                        }
                    }
                }
            }
            if (doorOpenedprintQueue != null)
            {
                return doorOpenedprintQueue;
            }
            else
            {
                return currentPrinterPrintQueue;
            }
        }
    }
}
